Successful Zip Bomb attacks occur when an application expands untrusted archive files without controlling the size of the expanded data, which can
lead to denial of service. A Zip bomb is usually a malicious archive file of a few kilobytes of compressed data but turned into gigabytes of
uncompressed data. To achieve this extreme compression ratio, attackers will
compress irrelevant data (eg: a long string of repeated bytes).
Ask Yourself Whether
Archives to expand are untrusted and:
- There is no validation of the number of entries in the archive.
- There is no validation of the total size of the uncompressed data.
- There is no validation of the ratio between the compressed and uncompressed archive entry.
There is a risk if you answered yes to any of those questions.
Recommended Secure Coding Practices
- Define and control the ratio between compressed and uncompressed data, in general the data compression ratio for most of the legit archives is
1 to 3.
- Define and control the threshold for maximum total size of the uncompressed data.
- Count the number of file entries extracted from the archive and abort the extraction if their number is greater than a predefined threshold, in
particular it’s not recommended to recursively expand archives (an entry of an archive could be also an archive).
Sensitive Code Example
using var zipToOpen = new FileStream(@"ZipBomb.zip", FileMode.Open);
using var archive = new ZipArchive(zipToOpen, ZipArchiveMode.Read);
foreach (ZipArchiveEntry entry in archive.Entries)
{
entry.ExtractToFile("./output_onlyfortesting.txt", true); // Sensitive
}
Compliant Solution
int THRESHOLD_ENTRIES = 10000;
int THRESHOLD_SIZE = 1000000000; // 1 GB
double THRESHOLD_RATIO = 10;
int totalSizeArchive = 0;
int totalEntryArchive = 0;
using var zipToOpen = new FileStream(@"ZipBomb.zip", FileMode.Open);
using var archive = new ZipArchive(zipToOpen, ZipArchiveMode.Read);
foreach (ZipArchiveEntry entry in archive.Entries)
{
totalEntryArchive ++;
using (Stream st = entry.Open())
{
byte[] buffer = new byte[1024];
int totalSizeEntry = 0;
int numBytesRead = 0;
do
{
numBytesRead = st.Read(buffer, 0, 1024);
totalSizeEntry += numBytesRead;
totalSizeArchive += numBytesRead;
double compressionRatio = totalSizeEntry / entry.CompressedLength;
if(compressionRatio > THRESHOLD_RATIO) {
// ratio between compressed and uncompressed data is highly suspicious, looks like a Zip Bomb Attack
break;
}
}
while (numBytesRead > 0);
}
if(totalSizeArchive > THRESHOLD_SIZE) {
// the uncompressed data size is too much for the application resource capacity
break;
}
if(totalEntryArchive > THRESHOLD_ENTRIES) {
// too much entries in this archive, can lead to inodes exhaustion of the system
break;
}
}
See